package repositories

import (
	"context"
	"github.com/pkg/errors"
	"go.didapinche.com/foundation/plat-echo/app/ops/models"
	"go.didapinche.com/foundation/plat-echo/app/ops/models/request"
	"go.didapinche.com/foundation/plat-echo/app/ops/models/response"
	"go.didapinche.com/time"
	"gorm.io/gorm"
	"strconv"
	"strings"
)

type ApiDataRepository interface {
	Create(ctx context.Context, apiData *models.ApiData) error
	Creates(ctx context.Context, apiData []*models.ApiData) error
	FindAll(ctx context.Context) ([]*models.ApiData, error)
	DeleteByID(ctx context.Context, ID uint64) error
	FindByID(ctx context.Context, ID uint64) (*models.ApiData, error)
	DeleteByTime(ctx context.Context, start time.Time) error
	DeleteByApiIDsAndTime(ctx context.Context, ids []uint64, start string) error
	Find(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, int, error)
	FindByDate(ctx context.Context, start, end time.Time, appName, apiName string, tp int) (*response.ApiData, error)
	FindStatistics(ctx context.Context, start, end time.Time, name string, groupID []string, level uint64, size, Type int) ([]*response.ApiData, error)
	FindStatisticsSla(ctx context.Context, start, end time.Time, name string, groupID []string, level uint64, size, Type int) ([]*response.ApiData, error)
	FindConutByHttpAndData(ctx context.Context, groups []string, start, end time.Time) ([]*response.StatItem, error)
	FindConutByZeusAndData(ctx context.Context, groups []string, start, end time.Time) ([]*response.StatItem, error)
	FindList(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, int, error)
	FindByApiIDs(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, error)
	FindList2(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, error)
}

type apiDataRepository struct {
	db *gorm.DB
}

func NewApiDataRepository(db *gorm.DB) ApiDataRepository {
	return &apiDataRepository{
		db: db,
	}
}

func (d *apiDataRepository) Create(ctx context.Context, apiData *models.ApiData) error {
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).Create(apiData).Error; err != nil {
		return errors.Wrap(err, "create apiData error")
	}
	return nil
}
func (d *apiDataRepository) Creates(ctx context.Context, apiData []*models.ApiData) error {
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).CreateInBatches(&apiData, 1000).Error; err != nil {
		return errors.Wrap(err, "create apiData error")
	}
	return nil
}

func (d *apiDataRepository) FindAll(ctx context.Context) (result []*models.ApiData, err error) {
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).Find(&result).Error; err != nil {
		return nil, errors.Wrap(err, "find all apiData error")
	}
	return result, nil
}

func (d *apiDataRepository) DeleteByID(ctx context.Context, ID uint64) error {
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).Delete(models.ApiData{}, "id = ?", ID).Error; err != nil {
		return errors.Wrap(err, "delete apiData error")
	}
	return nil
}

func (d *apiDataRepository) FindByID(ctx context.Context, ID uint64) (*models.ApiData, error) {
	var apiData = new(models.ApiData)
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).Where("id = ?", ID).First(&apiData).Error; err != nil {
		return nil, errors.Wrap(err, "delete apiData error")
	}
	return apiData, nil
}

func (d *apiDataRepository) DeleteByTime(ctx context.Context, start time.Time) error {
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).Delete(models.ApiData{}, "create_time < ?", start).Error; err != nil {
		return errors.Wrap(err, "DeleteByTime apiData error")
	}
	return nil
}
func (d *apiDataRepository) DeleteByApiIDsAndTime(ctx context.Context, ids []uint64, start string) error {
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).Where("create_time < ? and api_id in ?", start, ids).Delete(models.ApiData{}).Error; err != nil {
		return errors.Wrap(err, "DeleteByApiIDsAndTime apiData error")
	}
	return nil
}

func (d *apiDataRepository) Find(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, int, error) {
	datas := make([]*response.ApiData, 0)
	var count int
	query := ""
	queryHistory := ""
	if request.TemplateType == 1 {
		query += " where api.http_path like  " + "'%" + request.Query + "%' and api.type=0"
		queryHistory = " and t_alert_history.type=0 "
	} else if request.TemplateType == 2 {
		query += " where api.zeus_method like " + "'%" + request.Query + "%' and api.type=1"
		queryHistory = " and t_alert_history.type=1 "
	} else {
		query += " where 1=1 "
	}
	if request.AppName != "" {
		query += " and api.app_name='" + request.AppName + "'"
	}
	if request.Level != 0 {
		query += " and api.level=" + strconv.Itoa(request.Level)
	}
	if request.Level != 0 {
		query += " and api.level=" + strconv.Itoa(request.Level)
	}
	if request.Order == "" {
		request.Order = "sla desc"
	}
	db := d.db.Begin()
	if err := db.WithContext(ctx).
		Raw(`select sql_calc_found_rows
                           api.id,
                           api.app_id,
                           api.app_name,
                           api.level,
                           api.http_method,
                           api.zeus_method,
                           api.http_path,
                           api.zeus_service,
                           api.owner,
                           api.disable_graph,
                           sum(data.tp99)                tp99,
                           sum(data.tp95)                tp95,
                           sum(data.qps)                 qps,
                           sum(data.total_request_count) total_request_count,
                           sum(data.error_count)         error_count,
                           max(data.tp99)                tp99_max,
                           max(data.tp95)                tp95_max,
                           max(data.qps)                 qps_max,
                           ta.duration                   sla
from t_api api
         left join (select sum(duration) duration, api_id
                    from t_alert_history
                    where (t_alert_history.start_time > ? and
                           t_alert_history.end_time < ? and t_alert_history.template_id in?)
                      and t_alert_history.duration > 0 `+queryHistory+`
                    group by t_alert_history.api_id) ta
                   on (api.id = ta.api_id)
         left join t_api_data data on api.id = data.api_id and
                                      (data.create_time between ? and ?)
`+query+`
group by api.id, api.app_id, api.app_name, api.level, api.http_method, api.zeus_method, api.http_path, api.zeus_service,
         api.owner,ta.duration
order by `+request.Order+`
limit ?,?`, request.Start, request.End, request.TemplateIds, request.Start, request.End, (request.PageNum-1)*request.PageSize, request.PageNum*request.PageSize).
		Scan(&datas).
		Error; err != nil {
		return nil, 0, errors.Wrap(err, "find apiData error")
	}
	if err := db.Raw("SELECT FOUND_ROWS() count").Scan(&count).Error; err != nil {
		return nil, 0, errors.Wrap(err, "find apiData count error")
	}
	return datas, count, nil
}

func (d *apiDataRepository) FindByDate(ctx context.Context, start, end time.Time, appName, apiName string, tp int) (*response.ApiData, error) {
	apiData := new(response.ApiData)
	query := ""
	if appName != "" {
		query = " and app_name like '%" + appName + "%' "
	}
	if apiName != "" && tp == 0 {
		query += " and http_path like '%" + appName + "%'"
	} else if apiName != "" && tp == 1 {
		query += " and zeus_method like '%" + appName + "%'"
	}
	if err := d.db.WithContext(ctx).
		Raw("select sum(qps) qps,sum(error_count) error_count, sum(total_request_count) total_request_count from t_api_data where (create_time between ? and ?) and `type`=?"+query, start, end, tp).
		Scan(apiData).
		Error; err != nil {
		return nil, errors.Wrap(err, "find apiData FindByDate error")
	}
	return apiData, nil
}
func (d *apiDataRepository) FindStatistics(ctx context.Context, start, end time.Time, name string, groupID []string, level uint64, size, Type int) ([]*response.ApiData, error) {
	apiDatas := make([]*response.ApiData, 0)
	query := ""
	if level != 0 {
		query = " and level=" + strconv.Itoa(int(level))
	}
	if err := d.db.WithContext(ctx).
		Table(models.ApiDataTableName).
		Select("http_method,http_path,zeus_method,zeus_service,api_id,"+name).
		Where("dev_group_id in ? and (create_time between ? and ?) and `type`=? and api_id>0 "+query, groupID, start, end, Type).
		Group("http_method, http_path, zeus_method, zeus_service,api_id").
		Having("`value`>0").
		Order("value desc").Limit(size).Scan(&apiDatas).Error; err != nil {
		return nil, errors.Wrap(err, "find apiData FindStatistics error")
	}
	return apiDatas, nil
}
func (d *apiDataRepository) FindStatisticsSla(ctx context.Context, start, end time.Time, name string, groupID []string, level uint64, size, Type int) ([]*response.ApiData, error) {
	apiDatas := make([]*response.ApiData, 0)
	query := ""
	if level != 0 {
		query = " and h.level=" + strconv.Itoa(int(level))
	}
	if err := d.db.WithContext(ctx).
		Table("t_alert_history h").
		Select("http_method,http_path,zeus_method,zeus_service,h.api_id,"+name).
		Joins("left join t_api api on api.id=h.api_id ").
		Where("h.dev_group_id in ? and (h.date between ? and ?) and h.type=? and api_id>0"+query, groupID, start, end, Type).
		Group("h.api_id").
		Having("`value`>0").
		Order("value desc").Limit(size).Scan(&apiDatas).Error; err != nil {
		return nil, errors.Wrap(err, "find apiData FindStatistics error")
	}
	return apiDatas, nil
}
func (d *apiDataRepository) FindConutByHttpAndData(ctx context.Context, groups []string, start, end time.Time) ([]*response.StatItem, error) {
	statItems := make([]*response.StatItem, 0)
	if err := d.db.WithContext(ctx).
		Raw("select count(*) http_api_count,level,"+
			"sum(total_request_count) http_request_count,"+
			"sum(qps) http_qps, "+
			"sum(error_count) http_error_count,"+
			"max(qps) http_qps_max"+
			" from t_api_data where dev_group_id in ? and (create_time between ? and ? ) and `type`=0 "+
			"group by level", groups, start, end).
		Scan(&statItems).Error; err != nil {
		return nil, errors.Wrap(err, "find apiData FindConutByHttpAndData error")
	}
	return statItems, nil
}

func (d *apiDataRepository) FindConutByZeusAndData(ctx context.Context, groups []string, start, end time.Time) ([]*response.StatItem, error) {
	statItems := make([]*response.StatItem, 0)
	if err := d.db.WithContext(ctx).
		Raw("select count(*) zeus_api_count,level,"+
			"sum(total_request_count) zeus_request_count,"+
			"sum(qps) zeus_qps,"+
			"sum(error_count) zeus_error_count,"+
			"max(qps) zeus_qps_max"+
			" from t_api_data where dev_group_id in ? and (create_time between ? and ? ) and `type`=1 "+
			"group by level", groups, start, end).
		Scan(&statItems).Error; err != nil {
		return nil, errors.Wrap(err, "find apiData FindConutByZeusAndData error")
	}
	return statItems, nil
}

func (d *apiDataRepository) FindList(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, int, error) {
	datas := make([]*response.ApiData, 0)
	var count int64
	query := ""
	if request.TemplateType == 1 {
		query += " `type`=0 and http_path!=''"
		if request.Query != "" {
			query += " and http_path like '%" + request.Query + "%'"
		}
	} else if request.TemplateType == 2 {
		query += " `type`=1"
		if request.Query != "" {
			query += " and zeus_method like '%" + request.Query + "%'"
		}
	}
	if request.AppName != "" {
		query += " and app_name like '%" + request.AppName + "%'"
	}
	if request.Level != 0 {
		query += " and level=" + strconv.Itoa(request.Level)
	}
	order := request.Order
	if request.Order == "" || strings.Contains(request.Order, "sla") {
		order = "qps desc"
	}
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).
		Select("api_id,app_id,app_name,level,http_method,zeus_method,http_path,zeus_service,owner,sum(tp99)/count(*) tp99, sum(tp95)/count(*) tp95, sum(qps) qps, sum(total_request_count) total_request_count, sum(error_count) error_count, max(tp99) tp99_max, max(tp95) tp95_max, max(qps) qps_max").
		Where(query+" and (create_time between ? and ?)  and api_id>0", request.Start, request.End).
		Group("app_name,http_method,zeus_method,http_path,zeus_service").Count(&count).
		Order(order).Offset(int((request.PageNum - 1) * request.PageSize)).
		Limit(int(request.PageSize)).Scan(&datas).Error; err != nil {
		return nil, 0, errors.Wrap(err, "FindList apiData error")
	}
	return datas, int(count), nil
}

func (d *apiDataRepository) FindByApiIDs(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, error) {
	datas := make([]*response.ApiData, 0)
	if err := d.db.WithContext(ctx).Table(models.ApiDataTableName).
		Select("api_id,app_id,app_name,level,http_method,zeus_method,http_path,zeus_service,owner,sum(tp99) tp99, sum(tp95) tp95, sum(qps) qps, sum(total_request_count) total_request_count, sum(error_count) error_count, max(tp99) tp99_max, max(tp95) tp95_max, max(qps) qps_max").
		Where("(create_time between ? and ?) and api_id in ?", request.Start, request.End, request.ApiIDs).
		Group("api_id").
		Scan(&datas).Error; err != nil {
		return nil, errors.Wrap(err, "FindByApiIDs apiData error")
	}
	return datas, nil
}

//pageNum和pageSize不做处理
func (d *apiDataRepository) FindList2(ctx context.Context, request *request.ApiSlaRequest) ([]*response.ApiData, error) {
	datas := make([]*response.ApiData, 0)
	query := ""
	if request.TemplateType == 1 {
		query += " `type`=0 and http_path!=''"
		if request.Query != "" {
			query += " and http_path like '%" + request.Query + "%'"
		}
	} else if request.TemplateType == 2 {
		query += "`type`=1"
		if request.Query != "" {
			query += " and zeus_method like '%" + request.Query + "%'"
		}
	}
	if request.AppName != "" {
		query += " and app_name like '%" + request.AppName + "%'"
	}
	if request.Level != 0 {
		query += " and level=" + strconv.Itoa(request.Level)
	}
	order := request.Order
	if request.Order == "" || strings.Contains(request.Order, "sla") {
		order = "qps desc"
	}
	db := d.db.WithContext(ctx).Table(models.ApiDataTableName).
		Select("api_id,app_id,app_name,level,http_method,zeus_method,http_path,zeus_service,owner,sum(tp99)/count(*) tp99, sum(tp95)/count(*) tp95, sum(qps) qps, sum(total_request_count) total_request_count, sum(error_count) error_count, max(tp99) tp99_max, max(tp95) tp95_max, max(qps) qps_max").
		Where(query+" and (create_time between ? and ?) ", request.Start, request.End)
	if len(request.ApiIDs) > 0 {
		db = db.Where("api_id not in ?", request.ApiIDs)
	}
	if err := db.Group("app_name,http_method,zeus_method,http_path,zeus_service").
		Order(order).Offset(int(request.PageNum)).
		Limit(int(request.PageSize)).Scan(&datas).Error; err != nil {
		return nil, errors.Wrap(err, "FindList apiData error")
	}
	return datas, nil
}
